package com.oromwin.repos.server.thread;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;

import com.oromwin.repos.common.Logger;
import com.oromwin.repos.common.ReposException;
import com.oromwin.repos.common.Server;
import com.oromwin.repos.common.Server.LockType;
import com.oromwin.repos.common.helper.IOHelper;
import com.oromwin.repos.server.LockObject;
import com.oromwin.repos.server.ReposSocketServer;
import com.oromwin.repos.server.request.Request;
import com.oromwin.repos.server.request.file.CreateFileRequest;
import com.oromwin.repos.server.response.CreateResponse;
import com.oromwin.repos.server.response.NormalResponse;
import com.oromwin.repos.server.response.Response;

/*
 * XXX: how about splitting input output thread, need to share client socket 
 */
public class SocketThread extends Thread {
    private transient Log log = Logger.getLog(SocketThread.class);
    private Socket _socket = null;
    private Map<String, LockObject> _lock = null;
    private LockObject _lockObject = null;
    private static boolean _shutdown = false;

    public synchronized static boolean isShutdown() {
        return _shutdown;
    }

    public synchronized static void setShutdown(boolean shutdown) {
        SocketThread._shutdown = shutdown;
    }

    public SocketThread(Socket socket) throws IOException {
        _socket = socket;
        _lock = new HashMap<String, LockObject>();
    }

    public void run() {
        Request req = null;
        Response res = null;
        String className = null;
        DataInputStream dis = null;
        DataOutputStream dos = null;

        ReposSocketServer.incrementCount();
        try {
            // detect request packet
            dis = new DataInputStream(new BufferedInputStream(_socket.getInputStream()));
            className = IOHelper.readUTF8(dis);
            if (log.isDebugEnabled()) log.debug(String.format("Request {%s} has recieved.", className));

            // receive request with concurrent locking
            req = (Request) Class.forName(className).newInstance();
            LockType lockType = Server.getLockTypeOfPacket(req);
            // XXX : in case of file must use received uuid() except CreateFileRequest
            String id = Server.identifyUniqueId(dis, req);
            if (log.isDebugEnabled()) log.debug(String.format("Identifier {%s} has assigned.", id));

            synchronized (_lock) { // global lock
                _lockObject = _lock.get(id);
                if (_lockObject == null) {
                    _lockObject = new LockObject(lockType, id);
                    _lock.put(id, _lockObject);

                } else if (_lockObject.getLockType() == Server.LockType.READ && lockType == Server.LockType.READ) {
                    _lockObject = new LockObject(lockType, id);
                    _lock.put(id, _lockObject);

                } else {
                    if (log.isErrorEnabled()) log.error("Read request came from client while writing or vice versa");
                }
            }
            if (log.isDebugEnabled()) log.debug(String.format("LockObject {%s} has assigned successfully.", _lockObject));

            synchronized (_lockObject) { // local lock
                try {
                    req.receive(dis, _lockObject);
                    if (isShutdown()) {
                        res = new CreateResponse(false, "Repository Server is shutting down now.", id);
                    } else {
                        res = req.run();
                    }
                } finally {
                    _lock.remove(id);
                }
            }
            if (log.isDebugEnabled()) log.debug(String.format("Request {%s} received {%s}.", res.getDescription(), res.getResult()));

        } catch (IOException e) {
            String message = String.format("Failed to getInputStream or readUTF8, {%s}", _socket.toString());
            Logger.errorWithThrowable(log, e, message);
            res = new NormalResponse(false, message);
        } catch (ClassNotFoundException e) {
            String message = String.format("Requested Packet Class not found, {%s}", className);
            Logger.errorWithThrowable(log, e, message);
            res = new NormalResponse(false, message);
        } catch (InstantiationException e) {
            String message = String.format("Failed to invoke newInstance, {%s()}", className);
            Logger.errorWithThrowable(log, e, message);
            res = new NormalResponse(false, message);
        } catch (IllegalAccessException e) {
            String message = String.format("Failed to invoke newInstance for illegal access, {%s()}", className);
            Logger.errorWithThrowable(log, e, message);
            res = new NormalResponse(false, message);
        } catch (ArrayIndexOutOfBoundsException e) {
            String message = String.format("ArrayIndexOutOfBoundsException {%s()}", className);
            Logger.errorWithThrowable(log, e, message);
            res = new NormalResponse(false, message);
        } catch (ReposException e) {
            Logger.errorWithThrowable(log, e, "Failed to receive or run request");
            String nullObjectId = "";
            if (req instanceof CreateFileRequest) {
                res = new CreateResponse(false, e.getMessage(), nullObjectId);
            } else {
                res = new NormalResponse(false, e.getMessage());
            }
        } catch (Exception e) {
            Logger.errorWithThrowable(log, e, "Unexpected exception occurred");
            String nullObjectId = "";
            if (req instanceof CreateFileRequest) {
                res = new CreateResponse(false, e.getMessage(), nullObjectId);
            } else {
                res = new NormalResponse(false, e.getMessage());
            }
        }
        // response
        try {
            dos = new DataOutputStream(new BufferedOutputStream(_socket.getOutputStream()));
            res.send(dos);
            if (log.isDebugEnabled()) log.debug(String.format("Result, {%s}", res.getResult()));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ReposException e) {
            e.printStackTrace();
        } finally {
            try { if (dos != null) dos.flush(); } catch (Exception e) {}
            try { if (dos != null) dos.close(); } catch (Exception e) {}
            try { if (dis != null) dis.close(); } catch (Exception e) {}
        }
        ReposSocketServer.decrementCount();
    }

}
